package com.xiyuan.smartutils.ids.impl;


import com.xiyuan.smartutils.ids.GenID;

/**
 * 梨花分布式id生成算法
 *
 * @author xiyuan-lgz
 */
public class PearId implements GenID {
    private static final long MAX_SEGMENT_ID = 0x1FFL;
    private static final long MAX_WORKER_ID = 0x3FFL;
    private static final long MAX_SEQUENCE = 0x1FFFL;
    private static final long TIME_MASK = 0x7FFFFFFFL;
    
    private static final int WORKERID_OFFSET = 13;
    private static final int SEGMENT_OFFSET = WORKERID_OFFSET + 10;
    private static final int TIME_OFFSET = SEGMENT_OFFSET + 9;
    
    /**
     * 起始时间
     */
    private final long startTime;
    /**
     * 段id
     */
    private final long segmentId;
    
    /**
     * 工作者id
     */
    private final long workerId;
    
    /**
     * 序号
     */
    private long sequence = 0;
    
    /**
     * 修改时间
     */
    private long updateTime;
    
    
    /**
     * 构造函数
     *
     * @param segmentId 分段id 取值范围： (0- 0x1FF)
     * @param workerId  工作者id, 取值范围: (0- 0x3FF)
     */
    public PearId(long segmentId, long workerId) {
        if (segmentId < 0 || segmentId > MAX_SEGMENT_ID) {
            throw new IllegalArgumentException("segmentId must between 0 and " + MAX_SEGMENT_ID);
        }
        
        if (workerId < 0 || workerId > MAX_WORKER_ID) {
            throw new IllegalArgumentException("workerId must between 0 and " + MAX_WORKER_ID);
        }
        
        this.segmentId = segmentId;
        this.workerId = workerId;
        this.startTime = this.updateTime = System.currentTimeMillis() / 1000;
    }
    
    public PearId() {
        this(0, 0);
    }
    
    public Long next() {
        long curSecond = System.currentTimeMillis() / 1000 - startTime;
        long sequenceValue = 0;
        synchronized (this) {
            if (curSecond > updateTime) {
                sequenceValue = sequence = 1;
            }
            else {
                if (sequence == MAX_SEQUENCE) {
                    ++updateTime;
                    sequenceValue = sequence = 1;
                }
                else {
                    sequenceValue = ++sequence;
                }
            }
        }
        return ((curSecond & TIME_MASK) << TIME_OFFSET) |
                (segmentId << SEGMENT_OFFSET) |
                (workerId << WORKERID_OFFSET) |
                sequenceValue;
    }
    
    @Override
    public String stringId() {
        return String.valueOf(next());
    }
}
